1.事件监听

  • 语法

    元素对象.addEventListener("事件类型",执行的函数)
    
  • 监听三要素

    • 事件源:那个dom元素被事件触发了,要获取dom元素
    • 事件类型:用什么方式触发
    • 事件调用的函数:要做什么事
  • 实例

    <button>按钮<button>
    <script>
        const btn = document.querySelector(".btn")
        btn.addEventListener("click",function(){
            alert("点击了")
        })
    </script>
    
  • 案例

image-20230810202702614.png

  <body>
      <h2>随机点名</h2>
      <div class="box">
          <span>名字是:</span>
          <div class="qs">这里显示姓名</div>
      </div>
      <div class="btns">
          <button class="start">开始</button>
          <button class="end">结束</button>
      </div>

      <script>
          // 数据数组
          const arr = ['马超', '黄忠', '赵云', '关羽', '张飞']
          // 获取元素
          const start = document.querySelector(".start")
          const end = document.querySelector(".end")
          const content = document.querySelector(".qs")
          // 设置单机事件
          let timerId = 0
          let random
          start.addEventListener("click",function(){
              timerId = setInterval(function(){
                  random = parseInt(Math.random()*arr.length)
                  content.innerText = arr[random]
              },50)
          })
          end.addEventListener("click",function(){
              clearInterval(timerId)
              arr.splice(random,1)
              if(arr.length == 1){
                  start.disabled = end.disabled = true
              }
          })

      </script>
  </body>
  • 事件监听版本

    • DOM L0
    元素.onclick = function(){
    
    }
    
    • DOM L2
    元素.addEventListener("click",function(){
    
    })
    
    • 区别

      on方式会被覆盖,addEventListener方式可以绑定多次,拥有事件更多特性

2.事件类型

  • 鼠标事件

    • 鼠标单机——click
    • 鼠标经过——mouseenter
    • 鼠标离开——mouseleave
    <body>
        <div></div>
        <script>
            const div = document.querySelector("div")
            // 鼠标悬浮
            div.addEventListener("mouseenter",function(){
                console.log("鼠标放在了div上面")
            })
            // 鼠标离开
            div.addEventListener("mouseleave",function(){
                console.log("鼠标离开")
            })
        </script>
    </body>
    
  • 焦点事件

    • 获取焦点——focus
    • 失去焦点——blur
  • 键盘事件

    • 键盘按下触发——keydown
    • 键盘抬起触发——keyup
    <body>
        <input type="text" name="" id="">
        <script>
            const input = document.querySelector("input")
            input.addEventListener("keydown",function(){
                console.log("键盘按下")
            })
            input.addEventListener("keyup",function(){
                console.log("键盘弹起")
            })
            // 用户输入文本事件
            input.addEventListener("input",function(){
                console.log(input.value)
            })
        </script>
    </body>
    
  • 文本事件

    • 用户输入事件——input
  • 案例:完整轮播图

    ```html

    <div class="slider">
        <div class="slider-wrapper">
            <img src="../images/slider01.jpg" alt="" />
        </div>
        <div class="slider-footer">
            <p>对人类来说会不会太超前了?</p>
            <ul class="slider-indicator">
                <li class="active"></li>
                <li></li>
                <li></li>
                <li></li>
                <li></li>
                <li></li>
                <li></li>
                <li></li>
            </ul>
            <div class="toggle">
                <button class="prev">&lt;</button>
                <button class="next">&gt;</button>
            </div>
        </div>
    </div>
    <script>
        // 1. 初始数据
        const data = [{
                url: '../images/slider01.jpg',
                title: '对人类来说会不会太超前了?',
                color: 'rgb(100, 67, 68)'
            },
            {
                url: '../images/slider02.jpg',
                title: '开启剑与雪的黑暗传说!',
                color: 'rgb(43, 35, 26)'
            },
            {
                url: '../images/slider03.jpg',
                title: '真正的jo厨出现了!',
                color: 'rgb(36, 31, 33)'
            },
            {
                url: '../images/slider04.jpg',
                title: '李玉刚:让世界通过B站看到东方大国文化',
                color: 'rgb(139, 98, 66)'
            },
            {
                url: '../images/slider05.jpg',
                title: '快来分享你的寒假日常吧~',
                color: 'rgb(67, 90, 92)'
            },
            {
                url: '../images/slider06.jpg',
                title: '哔哩哔哩小年YEAH',
                color: 'rgb(166, 131, 143)'
            },
            {
                url: '../images/slider07.jpg',
                title: '一站式解决你的电脑配置问题!!!',
                color: 'rgb(53, 29, 25)'
            },
            {
                url: '../images/slider08.jpg',
                title: '谁不想和小猫咪贴贴呢!',
                color: 'rgb(99, 72, 114)'
            },
        ]
        // 设置右按钮事件
        const next = document.querySelector(".toggle .next")
        const img = document.querySelector(".slider-wrapper img")
        const p = document.querySelector(".slider-footer p")
        const div = document.querySelector(".slider-footer")
    
        // 抽取公共部分拿来复用
        function render(){
            img.src = data[i].url
            p.innerText = data[i].title
            div.style.backgroundColor = data[i].color
            // 取消圆点样式
            document.querySelector(".active").classList.remove("active")
            // 添加圆点样式
            document.querySelector(`ul li:nth-child(${i + 1})`).classList.add("active")
        }
    
      let i = 0
      next.addEventListener("click", function () {
          i++
          if (i >= data.length) {
              i = 0
          }
          render()
      })
      // 设置左按钮事件
      const prev = document.querySelector(".toggle .prev")
      prev.addEventListener("click",function(){
          i--
          if(i <= 0){
              i = data.length - 1
          }
          render()
      })
      // 轮播定时
      let timerId = setInterval(function(){
          next.click()
      },1000)
      // 鼠标悬浮时暂停定时,离开时开启定时
      const box = document.querySelector(".slider")
      box.addEventListener("mouseenter",function(){
          clearInterval(timerId)
      })
      box.addEventListener("mouseleave",function(){
          timerId = setInterval(function(){
              next.click()
          },1000)
      })
  </script>

</body>


- 案例

![image-20230810202732570.png](images/image-20230810202732570.png)

  ```html
  <body>
      <div class="mi">
          <input type="search" placeholder="小米笔记本">
          <ul class="result-list">
              <li><a href="#">全部商品</a></li>
              <li><a href="#">小米11</a></li>
              <li><a href="#">小米10S</a></li>
              <li><a href="#">小米笔记本</a></li>
              <li><a href="#">小米手机</a></li>
              <li><a href="#">黑鲨4</a></li>
              <li><a href="#">空调</a></li>
          </ul>
      </div>
      <script>
          const input = document.querySelector("[type='search']")
          const ul = document.querySelector(".result-list")
          input.addEventListener("focus",function(){
              ul.style.display = "block"
              input.classList.add("search")
          })
          input.addEventListener("blur",function(){
              ul.style.display = "none"
              input.classList.remove("search")
          })
      </script>
  </body>

3.事件对象

  • 语法

    • 在事件绑定的回调函数的第一个参数就是事件对象
    • 一般命名为:event、ev、e
    元素.addEventListener("click",function(e){
    
    })
    
    <body>
        <input type="text" name="" id="">
        <script>
            const input = document.querySelector("input")
            input.addEventListener("keyup",function(e){
                if(e.key == "Enter"){
                    console.log("按下了回车键")
                }
            })
        </script>
    </body>
    
  • 部分常用属性

    • type:获取当前的事件类型
    • clientX/clientY:获取光标相对于浏览器可见窗口左上角位置
    • offsetX/offsetY:获取光标相对于当前DOM元素左上角位置
    • key:用户按下的键盘键的值
  • 案例

image-20230810202740994.png

  <body>
      <div class="wrapper">
          <i class="avatar"></i>
          <textarea id="tx" placeholder="发一条友善的评论" rows="2" maxlength="200"></textarea>
          <button>发布</button>
      </div>
      <div class="wrapper">
          <span class="total">0/200字</span>
      </div>
      <div class="list">
          <div class="item" style="display: none;">
              <i class="avatar"></i>
              <div class="info">
                  <p class="name">清风徐来</p>
                  <p class="text">大家都辛苦啦,感谢各位大大的努力,能圆满完成真是太好了[笑哭][支持]</p>
                  <p class="time">2022-10-10 20:29:21</p>
              </div>
          </div>
      </div>
      <script>
          const text = document.querySelector("#tx")
          const total = document.querySelector(".total")
          text.addEventListener("focus", function () {
              total.style.opacity = "1"
          })
          text.addEventListener("blur", function () {
              total.style.opacity = "0"
          })
          text.addEventListener("input", function () {
              total.innerText = `${text.value.length}/200字`
          })

          const item = document.querySelector(".item")
          const content = document.querySelector(".text")
          const bt = document.querySelector("button")
          bt.addEventListener("click", function () {
              if (text.value.trim()) {
                  item.style.display = "block"

                  content.innerText = text.value
              }
              text.value = ""
              total.innerText = '0/200字'
          })
          text.addEventListener("keyup", function (e) {
              if (e.key == "Enter") {
                  bt.click()
              }
          })
      </script>
  </body>

4.环境对象

  • 概述

    • 指的是函数内部特殊的变量this,它代表当前函数运行时所处的环境
  • 作用

    • 函数的调用方式不同,this指代的对象也不同
    • 谁调用,this就是谁
  • 补充:伪类选择器

    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
        <style>
            .ck:checked{
                width: 30px;
                height: 30px;
            }
        </style>
    </head>
    <body>
        <input type="checkbox" name="" id="" class="ck">
        <input type="checkbox" name="" id="" class="ck">
        <input type="checkbox" name="" id="" class="ck">
    </body>
    

5.综合案例

image-20230810202751842.png

<body>
  <div class="tab">
    <div class="tab-nav">
      <h3>每日特价</h3>
      <ul>
        <li><a class="active" href="javascript:;">精选</a></li>
        <li><a href="javascript:;">美食</a></li>
        <li><a href="javascript:;">百货</a></li>
        <li><a href="javascript:;">个护</a></li>
        <li><a href="javascript:;">预告</a></li>
      </ul>
    </div>
    <div class="tab-content">
      <div class="item active"><img src="./images/tab00.png" alt="" /></div>
      <div class="item"><img src="./images/tab01.png" alt="" /></div>
      <div class="item"><img src="./images/tab02.png" alt="" /></div>
      <div class="item"><img src="./images/tab03.png" alt="" /></div>
      <div class="item"><img src="./images/tab04.png" alt="" /></div>
    </div>
  </div>
  <script>
        const as = document.querySelectorAll(".tab-nav li a")
        for(let i = 0; i<as.length;i++){
            as[i].addEventListener("mouseenter",function(){
                // 清楚active类名
                document.querySelector(".tab-nav .active").classList.remove("active")
                // 添加active类名在当前鼠标悬浮的标签
                this.classList.add("active")

                // 清楚 div块内的active类名
                document.querySelector(".tab-content .active").classList.remove("active")
                // 添加
                document.querySelector(`.tab-content div:nth-child(${i + 1})`).classList.add("active")
            })
        }
  </script>
</body>

image-20230810202758883.png

<body>
    <table>
        <tr>
            <th class="allCheck">
                <input type="checkbox" name="" id="checkAll"> <span class="all">全选</span>
            </th>
            <th>商品</th>
            <th>商家</th>
            <th>价格</th>
        </tr>
        <tr>
            <td>
                <input type="checkbox" name="check" class="ck">
            </td>
            <td>小米手机</td>
            <td>小米</td>
            <td>¥1999</td>
        </tr>
        <tr>
            <td>
                <input type="checkbox" name="check" class="ck">
            </td>
            <td>小米净水器</td>
            <td>小米</td>
            <td>¥4999</td>
        </tr>
        <tr>
            <td>
                <input type="checkbox" name="check" class="ck">
            </td>
            <td>小米电视</td>
            <td>小米</td>
            <td>¥5999</td>
        </tr>
    </table>
    <script>
        // 全选按钮
        const allCheck = document.querySelector("#checkAll")

        const checks = document.querySelectorAll(".ck")
        allCheck.addEventListener("click", function () {
            for (let i = 0; i < checks.length; i++) {
                checks[i].checked = allCheck.checked
            }
        })
        for(let i = 0; i < checks.length; i++){
            checks[i].addEventListener("click",function(){
                allCheck.checked = document.querySelectorAll(".ck:checked").length == checks.length
            })
        }
    </script>
</body>

6.事件流

  • 事件流和两个阶段说明

    • 事件流指的是事件完整执行过程中的流动路径
    • 说明:假设页面存在一个div,当触发事件时,会经历捕获阶段、冒泡阶段
    • 实际工作都是使用事件冒泡为主

image-20230810202856143.png

  • 事件捕获

    • 从DOM的根元素开始去执行对应的事件(从外到里)
    • 语法
    DOM.addEventListener(事件类型,事件处理函数,是否使用捕获机制)
    
    • 说明
      • addEventListener第三个参数传入 true 代表是捕获阶段触发
      • 默认为 false
  • 事件冒泡

    • 当一个元素的事件被出发时,同样的事件将会在该元素的所有祖先元素中依次被触发。这一过程被称为事件冒泡
    <body>
        <div class="father">
            <div class="son"></div>
        </div>
        <script>
            const fa = document.querySelector(".father")
            const son = document.querySelector(".son")
    
            document.addEventListener("click",function(){
                alert("爷爷")
            })
            fa.addEventListener("click",function(){
                alert("父")
            })
            son.addEventListener("click",function(e){
                alert("儿")
            })
        </script>
    </body>
    
  • 阻止冒泡

    • 问题:默认冒泡模式的存在,所以容易导致事件影响到父级元素
    • 需求:想把事件限制在当前元素,就需要组织事件冒泡
    • 前提:组织事件冒泡需要拿到事件对象
    son.addEventListener("click",function(e){
        alert("儿")
        // 组织流动传播【组织冒泡继续向上】
        e.stopPropagation()
    })
    
    • 阻止默认行为
    e.preventDefault()
    
  • 解绑事件

    <body>
        <button id="l0">L0点击</button>
        <button id="l2">L2点击</button>
        <script>
            const bt = document.querySelector("#l0")
            // L0 版本
            bt.onclick = function(){
                alert("点击了")
                // 接触绑定
                bt.onclick = null
            }
            const btn = document.querySelector("#l2")
            function butt(){
                alert("点击了")
            }
            btn.addEventListener("click",butt)
            // 解除绑定
            btn.removeEventListener("click",butt)
        </script>
    </body>
    

    匿名函数无法被解绑

7.事件委托

  • 概述
    • 事件委托是利用事件流的特征解决一些开发需求的知识技巧
  • 优点
    • 减少注册次数,可以提高程序性能
  • 原理

    • 事件委托就是是利用事件冒泡的特点:给父元素注册事件,当触发子元素的时候,会冒泡到父元素身上,从而触发父元素的事件
  • 实现

    • 事件对象.target获得真正触发事件的元素对象,.target.tagName为该元素对象的标签名称
<body>
    <ul>
        <li>我是第1个li</li>
        <li>我是第2个li</li>
        <li>我是第3个li</li>
        <li>我是第4个li</li>
        <p>我不需要添加</p>
    </ul>
    <script>
        // 点击每个li 为当前li字体变为红色(通过冒泡来形成事件委托)
        const ul = document.querySelector("ul")
        ul.addEventListener("click",function(e){
            // e.target 点击的当前标签对象
            // 排除p标签 e.target.tagName 为点击当前的标签名
            if(e.target.tagName == "LI"){
                e.target.style.color = "red"
            }
        })
    </script>
</body>
  • 改造tabl栏切换
<body>
    <div class="tab">
        <div class="tab-nav">
            <h3>每日特价</h3>
            <ul>
                <li><a class="active" href="javascript:;" data-id="0">精选</a></li>
                <li><a href="javascript:;" data-id="1">美食</a></li>
                <li><a href="javascript:;" data-id="2">百货</a></li>
                <li><a href="javascript:;" data-id="3">个护</a></li>
                <li><a href="javascript:;" data-id="4">预告</a></li>
            </ul>
        </div>
        <div class="tab-content">
            <div class="item active"><img src="./images/tab00.png" alt="" /></div>
            <div class="item"><img src="./images/tab01.png" alt="" /></div>
            <div class="item"><img src="./images/tab02.png" alt="" /></div>
            <div class="item"><img src="./images/tab03.png" alt="" /></div>
            <div class="item"><img src="./images/tab04.png" alt="" /></div>
        </div>
    </div>
    <script>
        const ul = document.querySelector(".tab-nav ul")
        const divs = document.querySelectorAll(".tab-content div")
        ul.addEventListener("click",function(e){
            // 清楚 active类名
            if(e.target.tagName == "A"){
                document.querySelector(".tab-nav .active").classList.remove("active")
                // 添加当前点击li的类型active
                e.target.classList.add("active")

                // 大盒子点击切换
                // 清楚类名
                document.querySelector(".tab-content .active").classList.remove("active")
                divs[Number(e.target.dataset.id)].classList.add("active")
            }
        })
    </script>
</body>

8.其它事件

  • 页面加载事件:load

    监听页面所有资源加载完毕

    window.addEventListener("load",function(){
    
    })
    

    不光可以监听整个页面资源加载完毕,也可以针对某个资源绑定load事件

  • 页面加载事件:DOMContentLoaded

    监听页面DOM加载完毕

    document.addEventListener("DOMContentLoaded",function(){
    
    })
    
  • 元素滚动事件:scroll

    很多网页需要检测用户吧页面滚动到某个区域做一些处理

    window.addEventListener("scroll",function(){
    
    })
    

    监听某个元素的内部滚动直接给某个元素加即可

    • scrollLeft 和 scrollTop
      • 获取被卷去的大小
      • 获取元素内容往左、往上滚出去看不到的距离
      • 可读写
    <style>
        body {
            height:3000px
        }
        div {
            width: 200px;
            height: 200px;
            overflow:scroll;
            display: none;
            margin-top:200px
        }
    </style>
    <body>
        <div>
            我有很多很多文字我有很多很多文字我有很多很多文字
            我有很多很多文字我有很多很多文字我有很多很多文字
            我有很多很多文字我有很多很多文字我有很多很多文字
            我有很多很多文字我有很多很多文字我有很多很多文字
            我有很多很多文字我有很多很多文字我有很多很多文字
            我有很多很多文字我有很多很多文字我有很多很多文字
            我有很多很多文字我有很多很多文字我有很多很多文字
            我有很多很多文字我有很多很多文字我有很多很多文字
        </div>
        <script>
            const div = document.querySelector("div")
            div.addEventListener("scroll",function(){
                // scrollTop被卷去的头部
                console.log(div.scrollTop)
            })
            window.addEventListener("scroll",function(){
                // 想知道页面滚动了多少像素,scrollTop
                // 获取html元素写法:document.documentElement
                const n = document.documentElement.scrollTop
                if(n >= 100){
                    div.style.display = "block"
                }else{
                    div.style.display = "none"
                }
            })
        </script>
    </body>
    

    document.documentElement 返回对象为HTML元素

  • 页面滚动事件——滚到到指定坐标

    • scrollTo() 方法可吧内容滚到到指定坐标
    // 让页面滚动到 y 轴1000像素的位置
    window.scrollTo(0,1000)
    
  • 页面尺寸事件:resize

    window.addEventListener("resize",function(){
    
    })
    
    • 检测屏幕宽度
    window.addEventListener("resize",function(){
        let w = document.documentElement.clientWidth
        console.log(w)
    })
    
  • 页面尺寸事件——获取元素宽高

    • 获取元素的可见部分宽高(不包含边框,margin,滚动条等)
    • clientWidth、clientHeight

9.元素尺寸与位置

  • 使用场景

    • 页面滚到到某个元素,可以做某些事情
  • 尺寸:获取宽高

    • 获取元素的自身宽高、包含元素自身设置的宽高、padding、border
    • offsetWidth和offsetHeight
    • 获取出来的是数值,方便计算
    • 如果盒子是隐藏的,结果为0
  • 尺寸:获取位置

    • 获取元素距离自己定位父级元素的左、上距离
    • offsetLeft和offsetTop只读属性
  • 案例:仿京东固定导航栏

image-20230810203052734.png

  <style>
          * {
              margin: 0;
              padding: 0;
              box-sizing: border-box;
          }

          .content {
              overflow: hidden;
              width: 1000px;
              height: 3000px;
              background-color: pink;
              margin: 0 auto;
          }

          .backtop {
              display: none;
              width: 50px;
              left: 50%;
              margin: 0 0 0 505px;
              position: fixed;
              bottom: 60px;
              z-index: 100;
          }

          .backtop a {
              height: 50px;
              width: 50px;
              background: url(./images/bg2.png) 0 -600px no-repeat;
              opacity: 0.35;
              overflow: hidden;
              display: block;
              text-indent: -999em;
              cursor: pointer;
          }

          .header {
              position: fixed;
              top: 0;
              left: 0;
              width: 100%;
              height: 80px;
              background-color: purple;
              text-align: center;
              color: #fff;
              line-height: 80px;
              font-size: 30px;
              transition: all .3s;
          }

          .sk {
              width: 300px;
              height: 300px;
              background-color: skyblue;
              margin-top: 500px;
          }
      </style>
  </head>

  <body>
      <div class="header">我是顶部导航栏</div>
      <div class="content">
          <div class="sk">秒杀模块</div>
      </div>
      <div class="backtop">
          <img src="./images/close2.png" alt="">
          <a href="javascript:;"></a>
      </div>
      <script>
          const header = document.querySelector(".header")
          const sk = document.querySelector(".sk")
          // 页面滚动事件
          window.addEventListener("scroll",function(){
              // 获取页面卷去的头部
              let n = document.documentElement.scrollTop
              // 当前页面滚动到秒杀模块的时候,改变头部top的值
              // 页面被卷去的头部 >= 秒杀模块的位置(offsetTop)
              header.style.top = n >= sk.offsetTop ? 0 : "-80px"
          })
      </script>
  </body>
  • 总结

image-20230810203107402.png

10.综合案例:电梯导航

image-20230810203124323.png

<script>
    // 页面滚动显示电梯导航栏
    (function () {
      const elevator = document.querySelector(".xtx-elevator")
      const entry = document.querySelector(".xtx_entry")
      window.addEventListener("scroll", function () {
        let n = document.documentElement.scrollTop
        // 判断超过entry的offsetTop则显示电梯导航栏
        elevator.style.opacity = n >= entry.offsetTop ? 1 : 0
      })
      // 点击电梯导航栏顶部来将滚动条设置相应效果
      const backTop = document.querySelector("#backTop")
      backTop.addEventListener("click", function () {
        // 将网页滚动条设置0即可
        document.documentElement.scrollTo(0, 0)



      })
    })()

    // 电梯导航栏每个点击效果
    (function () {
      const ul = document.querySelector(".xtx-elevator-list")
      ul.addEventListener("click", function (e) {
        // 设置类名效果 active
        if (e.target.targetName = "A" && e.target.dataset.name) {
          // 清楚类名   如果不存在,则DOM操作返回的元素为null
          const a = document.querySelector(".xtx-elevator-list .active")
          if (a) {
            a.classList.remove("active")
          }
          // 添加类名
          e.target.classList.add("active")
        }
        // 观察每个li标签内的a标签存在自定义属性,与要将滚动条到达的大盒子类名有所关联
        // 将滚动条设置到大盒子卷去的offsetTop即可  
        const box = document.querySelector(`.xtx_goods_${e.target.dataset.name}`)
        document.documentElement.scrollTop = box.offsetTop
      })

      // 大盒子找小盒子
      window.addEventListener("scroll", function () {
        // 清楚active
        const a = document.querySelector(".xtx-elevator-list .active")
        if (a) {
          a.classList.remove("active")
        }


        const news = document.querySelector(".xtx_goods_new")
        const popular = document.querySelector(".xtx_goods_popular")
        const brand = document.querySelector(".xtx_goods_brand")
        const category = document.querySelector(".xtx_goods_category")
        const topic = document.querySelector(".xtx_goods_topic")
        const n = document.documentElement.scrollTop
        if (n >= news.offsetTop && n <= popular.offsetTop) {
          document.querySelector("[data-name=new]").classList.add("active")
        }else if(n >= popular.offsetTop && n <= brand.offsetTop){
          document.querySelector("[data-name=popular]").classList.add("active")
        }else if(n >= brand.offsetTop && n <= category.offsetTop){
          document.querySelector("[data-name=brand]").classList.add("active")
        }else if(n >= category.offsetTop && n <= topic.offsetTop){
          document.querySelector("[data-name=category]").classList.add("active")
        }else if(n >= topic.offsetTop){
          document.querySelector("[data-name=topic]").classList.add("active")
        }
      })
    }())
  </script>

results matching ""

    No results matching ""